<template>
  <div class="ebook-reader">
    <div id="read"></div>
<!--    mousedown.left表示点击鼠标左键才会触发-->
    <div class="ebook-reader-mask"
         @click="onMaskClick"
         @touchmove="move"
         @touchend="moveEnd"
         @mousedown.left="onMouseEnter"
         @mousemove.left="onMouseMove"
         @mouseup.left="onMouseUp"
    ></div>
  </div>
</template>

<script>
import { ebookMixin } from '../../utils/mixin'
import Epub from 'epubjs'
import {
  getFontFamily,
  getFontSize,
  getTheme,
  getLocation,
  saveFontFamily,
  saveFontSize,
  saveTheme
} from '../../utils/localStroage'
import { flatten } from '../../utils/book'
import { getLocalForage } from '../../utils/localForage'

global.epub = Epub
export default {
  name: 'EbookReader',
  // 混合引用
  mixins: [ebookMixin],
  methods: {
    // ************************************ //
    // 1、鼠标进入
    // 2、鼠标进入后的移动
    // 3、鼠标从移动状态松开
    // 4、鼠标还原状态
    onMouseEnter (e) {
      // 1、鼠标进入
      this.mouseState = 1
      // 获取当前点击的时间戳
      this.mouseStateTime = e.timeStamp
      // 禁止鼠标事件的传播
      e.preventDefault()
      e.stopPropagation()
    },
    onMouseMove (e) {
      // 2、鼠标进入后的移动
      // 第一次移动先将鼠标状态改变，再次移动进行处理
      if (this.mouseState === 1) {
        this.mouseState = 2
      } else if (this.mouseState === 2) {
        // 初始化偏移量
        let offsetY = 0
        // 假如有第一次移动的坐标，则赋值，没有则将当前坐标Y轴坐标赋值给第一次Y轴坐标
        if (this.firstOffsetY) {
          // 每次移动都计算一次偏移量,这里可能会耗费一些性能，不过考虑到偏移量要实时计算，才能让拉动流畅，暂时无其他方法
          offsetY = e.clientY - this.firstOffsetY
          this.setOffsetY(offsetY)
        } else {
          // 获取第一个点击的触碰点的坐标
          this.firstOffsetY = e.clientY
        }
      }
      e.preventDefault()
      e.stopPropagation()
    },
    onMouseUp (e) {
      // 3、鼠标从移动状态松开
      // 此时鼠标状态非1即是2
      // 假如鼠标状态为移动状态2，松开即还原数据，假如非移动状态，则改为鼠标状态4
      if (this.mouseState === 2) {
        this.setOffsetY(0)
        this.firstOffsetY = null
        this.mouseState = 3
      } else {
        this.mouseState = 4
      }
      // 移动时间短，将认为是点击，改为状态4
      const time = e.timeStamp - this.mouseStateTime
      if (time < 100) {
        this.mouseState = 4
      }
      e.preventDefault()
      e.stopPropagation()
    },
    // ************************************ //
    // 1、移动端开始拖动
    // 2、松开拖动，还原状态
    move (e) {
      // 1、移动端开始拖动， 获取偏移量
      let offsetY = 0
      if (this.firstOffsetY) {
        // 当前触摸坐标减去第一次触摸坐标的Y轴值，即实时计算偏移量，
        offsetY = e.changedTouches[0].clientY - this.firstOffsetY
        this.setOffsetY(offsetY)
      } else {
        // 获取第一个点击的触碰点的坐标
        this.firstOffsetY = e.changedTouches[0].clientY
      }
      // 禁止调用默认方法
      e.preventDefault()
      e.stopPropagation()
    },
    moveEnd () {
      // 2、松开拖动，还原状态
      this.setOffsetY(0)
      this.firstOffsetY = null
    },
    // ************************************ //
    onMaskClick (e) {
      // 点击事件，只处理点击
      // 1、判断是否为点击事件，不是则不处理
      if (this.mouseState && (this.mouseState === 2 || this.mouseState === 3)) {
        return
      }
      // 2、获取当前点击X轴坐标
      const offsetX = e.offsetX
      const width = window.innerWidth
      // 3、判断点击是左部还是右部还是中部，同时触发相应的方法
      if (offsetX > 0 && offsetX < width * 0.3) {
        this.prevPage()
      } else if (offsetX > 0 && offsetX > width * 0.7) {
        this.nextPage()
      } else {
        this.toggleTitleAndMenu()
      }
    },
    // 上一页
    prevPage () {
      // 1、调用epub.js中的上一页方法，再异步调用刷新渲染页面
      if (this.rendition) {
        this.rendition.prev().then(() => {
          this.refreshLocation()
        })
        // 2、隐藏标题栏
        this.hideTitleAndMenu()
      }
    },
    // 下一页
    nextPage () {
      // 1、调用epub.js中的下一页方法，再刷新渲染页面
      if (this.rendition) {
        this.rendition.next().then(() => {
          this.refreshLocation()
        })
        // 2、隐藏标题栏
        this.hideTitleAndMenu()
      }
    },
    toggleTitleAndMenu () {
      // 1、假如菜单为显示状态，则隐藏具体菜单和设置字体拦
      if (this.menuVisible) {
        this.setSettingVisible(-1)
        this.setFontFamilyVisible(false)
      }
      // 2、将当前状态取反
      this.setMenuVisible(!this.menuVisible)
    },
    // ************************************ //
    // 1、初始化epub
    // 2、初始化book.Rendition
    // 3、加载封面
    // 4、根据字数进行分页
    // 1、起点，初始化epub
    initEpub (url) {
      this.book = new Epub(url)
      this.setCurrentBook(this.book)
      // 2、初始化book.Rendition，再进行相应操作
      this.initRenditon()
      // this.initGesture()
      // 3、加载封面
      this.parseBook()
      // 4、根据字数进行分页
      // 在电子书加载完毕后调用异步方法进行分页
      this.book.ready.then(() => {
        // generate的参数是分页以多少字数分页，默认值是150，此时为实际计算，具体算法是默认值750*当前屏幕与375的比
        // 假如屏幕大于375，则字数相应增加多750，否则则减少，同时再乘上当前字体大小和16的比，字体越大，字数越少
        return this.book.locations.generate(750 * (window.innerWidth / 375) * (getFontSize(this.fileName) / 16))
      }).then(locations => {
        // 初始化pageList
        this.navigation.forEach(nav => {
          nav.pageList = []
        })
        locations.forEach(item => {
          // 将方括号里的字符进行分组,返回两个结果，第一个是原item，第二个是匹配后的结果
          let loc = item.match(/\[(.*)]!/)[1]
          this.navigation.forEach(nav => {
            if (nav.href) {
              const href = nav.href.match(/^(.*)\.html$/)
              if (href) {
                if (href[1] === loc) {
                  nav.pageList.push(item)
                }
              }
            }
          })
        })
        // 定义章节页数
        let currentPage = 1
        this.navigation.forEach((nav, index) => {
          // 目录第一个即封面，定义首页
          if (index === 0) {
            nav.page = 1
          } else {
            nav.page = currentPage
          }
          // 每一章赋值后，页数加上该章节的页数，再自增1即可
          currentPage += nav.pageList.length + 1
        })
        this.setPagelist(locations)
        // 此时设置为分页完成，同时刷新地址
        this.setBookAvailable(true)
        this.refreshLocation()
      })
    },
    // 2、初始化book.Rendition
    initRenditon () {
      // 2、初始化book.Rendition, 将宽高传入
      this.rendition = this.book.renderTo('read', {
        width: innerWidth,
        height: innerHeight,
        // 微信兼容问题 bug会影响下面触发事件，可降级epubjs处理
        method: 'default'
        // 在微信端和苹果无法使用
        // flow: 'scrolled'
      })
      const location = getLocation(this.fileName)
      // 页面渲染以及后续初始化内容, 会自动判断location是否存在
      // 假如本地有数据，则跳转到之前阅读相应的页面，再进行各种初始化
      this.display(location, () => {
        // 初始化内容，都是获取本地数据，如果本地数据没有，则从vuex中将数据存储进去。
        // vuex的数据只有一次性，所以获取到本地数据后要存储到vuex中
        // init book, get the localstorage, if localstorage is null, vuex data save in localstorage
        // vuex data is disposable, so localstorage save in vuex
        this.initTheme()
        this.initFontSize()
        this.initFontFamily()
        // 2.4初始化全局样式
        this.initGlobalStyle()
        // 2.5刷新地址
        this.refreshLocation()
      })
      // 注册字体 English： register font
      this.rendition.hooks.content.register(contents => {
        // 由于epubjs的源码要求传入的是一个URL而不是路径,所有可以把该资源放在静态资源服务器上面
        Promise.all([
          contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/cabin.css`),
          contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/daysOne.css`),
          contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/montserrat.css`),
          contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/tangerine.css`)
        ]).then(() => {
          // console.log('字体加载完毕')
        })
      })
    },
    // 2.1 初始化主题
    initTheme () {
      // 2.1.1、获取本地主题设置，本地没有数据则存入第一个样式
      let defaultTheme = getTheme(this.fileName)
      if (!defaultTheme) {
        defaultTheme = this.themeList[0].name
        saveTheme(this.fileName, defaultTheme)
      }
      // 2.1.2、注册样式
      this.setDefaultTheme(defaultTheme)
      // 2.1.3、获取themeList主题列表中的每个主题样式，根据rendition.themes.register方法注册
      this.themeList.forEach(theme => {
        this.rendition.themes.register(theme.name, theme.style)
      })
      // 2.1.4选择样式
      this.rendition.themes.select(defaultTheme)
    },
    // 2.2初始化字体大小
    initFontSize () {
      let fontsize = getFontSize(this.fileName)
      if (!fontsize) {
        saveFontSize(this.fileName, this.defaultFontSize)
      } else {
        // 如果本地有数据则设置字体主题以及存储到VUEX中, 需要添加px
        // 此处有个BUG，无法设置初始化大小，可能是由于书本还没解析完毕
        // 下面用一个延迟执行来显示，但这会导致初次加载字体轻微闪烁
        setTimeout(() => {
          this.rendition.themes.fontSize(fontsize + 'px')
        }, 100)
        this.setDefaultFontSize(fontsize)
      }
    },
    // 2.3初始化字体样式
    initFontFamily () {
      let font = getFontFamily(this.fileName)
      if (!font) {
        saveFontFamily(this.fileName, this.defaultFontFamily)
      } else {
        // 如果本地有数据则设置字体主题以及存储到VUEX中
        this.rendition.themes.font(font)
        this.setDefaultFontFamily(font)
      }
    },
    initGesture () {
      // 如果要增加新功能，由于电子书中没有相应的绑定事件，所有需要使用蒙版手动创建，此时该初始化即无效
      this.rendition.on('touchstart', event => {
        this.touchStartX = event.changedTouches[0].clientX
        this.touchStartTime = event.timeStamp
      })
      this.rendition.on('touchend', event => {
        const offsetY = event.changedTouches[0].clientX - this.touchStartX
        const time = event.timeStamp - this.touchStartTime
        if (time < 500 && offsetY > 40) {
          this.prevPage()
        } else if (time < 500 && offsetY < -40) {
          this.nextPage()
        } else {
          this.toggleTitleAndMenu()
        }
        event.stopPropagation()
      })
    },
    // 3、加载封面
    parseBook () {
      // 3.1书本加载完毕后获取书本的cover对象
      this.book.loaded.cover.then(cover => {
        // 解析封面，结束返回cover对象,再生成链接
        this.book.archive.createUrl(cover, null).then(url => {
          this.setCover(url)
        })
      })
      // 3.2 获取数据基本信息
      this.book.loaded.metadata.then(metadata => {
        this.setMetadata(metadata)
      })
      // 3.3 处理目录
      this.book.loaded.navigation.then(navigation => {
        const navItem = flatten(navigation.toc)
        navItem.forEach(item => {
          // 3.3.1、为每个项添加level属性
          item.level = find(item)
        })
        // 3.3.2、找出有多少级目录
        function find (item, level = 0) {
          return !item.parent ? level : find(navItem.filter(parentItem => parentItem.id === item.parent)[0], ++level)
          // 找出其父目录，同时序列加一，直到找到根目录，返回出结果
        }
        // 3.3.3、将处理后的目录放进vuex中
        this.setNavigation(navItem)
      })
    }
    // ************************************ //
  },
  mounted () {
    // 新版本，使用离线缓存的电子书
    const books = this.$route.params.fileName.split('|')
    const fileName = books[1]
    getLocalForage(fileName, (err, blob) => {
      if (!err && blob) {
        this.setFileName(books.join('/')).then(() => {
          this.initEpub(blob)
        })
      } else {
        // 1、切割组合URL地址，并初始化epub
        this.setFileName(books.join('/')).then(() => {
          // 1、初始化epub，根据URL去获取静态服务器中的资源，然后传入epub进行渲染
          const url = process.env.VUE_APP_EPUB_URL + '/' + this.fileName + '.epub'
          this.initEpub(url)
        })
      }
    })
  }
}
</script>

<style lang="stylus" scoped>
@import "../../assets/styles/global.styl"
  .ebook-reader
    width: 100%
    height: 100%
    overflow: hidden
    .ebook-reader-mask
      position: absolute
      top: 0
      left: 0
      background: transparent
      width: 100%
      height: 100%
      z-index: 100
</style>
